{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Basic Tensor Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.1 – Creating a 2 dimensional vector\n",
    "import torch\n",
    "import numpy\n",
    "torch.tensor([[0.1, 0.2],[0.3, 0.4]])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.2 – Shape of a tensor\n",
    "a = torch.tensor([[0.1, 0.2],[0.3, 0.4]])\n",
    "a.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.3 – Shape of a tensor (continued)\n",
    "b = torch.tensor([[0.1, 0.2],[0.3, 0.4],[0.5, 0.6]])\n",
    "\n",
    "b\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.4 – Creating tensors with arbitary dimensions\n",
    "c = torch.tensor([[[0.1],[0.2]],[[0.3],[0.4]]])\n",
    "\n",
    "c.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.5 – Tensors with Numpy\n",
    "a = torch.tensor(numpy.array([[0.1, 0.2],[0.3, 0.4]]))\n",
    "\n",
    "a\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.6 - Tensors from Numpy\n",
    "import numpy as np\n",
    "a = np.array([1, 2, 3, 4, 5])\n",
    "tensor_a = torch.from_numpy(a)\n",
    "tensor_a\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.7 – Defining tensor datatypes\n",
    "a = torch.tensor([[0.1, 0.2],[0.3, 0.4]], dtype=torch.float32)\n",
    "a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = torch.tensor([[0.1, 0.2],[0.3, 0.4]], dtype=torch.float64)\n",
    "        \n",
    "a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = torch.tensor([[0.1, 0.2],[0.3, 0.4]], dtype=torch.float16)\n",
    "                \n",
    "a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.8 – Constructing tensors with random values\n",
    "r = torch.rand(2,2,2)\n",
    "\n",
    "r"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.9 – Tensors with zeros\n",
    "zeros = torch.zeros(2,2,3)\n",
    "\n",
    "zeros"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "zeros.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.10 – Tensors of ones\n",
    "ones = torch.ones(2,2,3)\n",
    "\n",
    "ones\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ones.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.11 – Identity maxtix tensors\n",
    "i = torch.eye(3)\n",
    "\n",
    "i\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "i.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.12 – Tensors filled with an arbitary value\n",
    "f = torch.full((3,3), 0.42)\n",
    "\n",
    "f"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.13 – Tensors with linearly spaced floating-point numbers\n",
    "lin = torch.linspace(0, 20, steps=5)\n",
    "\n",
    "lin"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.14- Tensors with logarithmically spaced floating-point numbers\n",
    "log = torch.logspace(-3, 3, steps=4)\n",
    "\n",
    "log"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.15 – Creating a tensor with dimensions similar to another tensor\n",
    "a = torch.tensor([[0.5, 0.5],[0.5, 0.5]])\n",
    "\n",
    "b = torch.zeros_like(a)\n",
    "\n",
    "b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "c = torch.ones_like(a)\n",
    "        \n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.16 – Tensor with int dtype\n",
    "i = torch.tensor([[1,2],[3,4]])\n",
    "\n",
    "i"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "i.dtype"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "i = torch.tensor([[1,2],[3,4]], dtype=torch.int)\n",
    "        \n",
    "i\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.17 – Tensors as a range of integers\n",
    "a = torch.arange(1,10, step=2)\n",
    "\n",
    "a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.18 – Tensors with random permutation of integers\n",
    "r = torch.randperm(10)\n",
    "r"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Tensor Munging Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.19 – Accessing individual members of a tensor\n",
    "a = torch.tensor([[1,2],[3,4]])\n",
    "\n",
    "a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(a[0][0])\n",
    "\n",
    "        \n",
    "print(a[0][1])\n",
    "        \n",
    "print(a[1][0])\n",
    "\n",
    "        \n",
    "print(a[1][1])\n",
    "        \n",
    "print(a.shape)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.20 – Accessing values from a tensor with a single value\n",
    "a = torch.tensor([[[0.42]]])\n",
    "\n",
    "print(a)\n",
    "\n",
    "print(a.shape)\n",
    "\n",
    "print(a.item())\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.21 – Reshaping a tensor\n",
    "a = torch.zeros(10)\n",
    "\n",
    "print(a)\n",
    "\n",
    "print(a.shape)\n",
    "\n",
    "b = a.view(2,5)\n",
    "\n",
    "print(b)\n",
    "print(b.shape)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.22 – Verifying the size of a tensor after reshaping\n",
    "a = torch.arange(1,10)\n",
    "\n",
    "print(a)\n",
    "\n",
    "print(a.shape)\n",
    "\n",
    "b = a.view(3,3)\n",
    "\n",
    "print(b)\n",
    "\n",
    "print(b.shape)\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.23 – Concatenating two tensors\n",
    "a = torch.zeros(2,2)\n",
    "\n",
    "print(a)\n",
    "print(a.shape)\n",
    "\n",
    "        \n",
    "b = torch.cat([a,a,a],0)\n",
    "        \n",
    "print(b)\n",
    "print(b.shape)\n",
    "                                                \n",
    "c = torch.cat([a,a,a],1)\n",
    "                                                \n",
    "print(c)\n",
    "print(c.shape)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.24 – Stacking tensors together\n",
    "a = torch.zeros(2,1)\n",
    "\n",
    "print(a)\n",
    "print(a.shape)\n",
    "\n",
    "\n",
    "b = torch.stack([a,a,a], 0)\n",
    "\n",
    "print(b)\n",
    "print(b.shape)\n",
    "                                           \n",
    "c = torch.stack([a,a,a], 1)\n",
    "                                           \n",
    "print(c)\n",
    "print(c.shape)\n",
    "\n",
    "\n",
    "d = torch.stack([a,a,a], 2)\n",
    "\n",
    "print(d)\n",
    "print(d.shape)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.25 – Chunking/chopping tensors \n",
    "a = torch.zeros(10, 1)\n",
    "\n",
    "print(a)\n",
    "print(a.shape)\n",
    "\n",
    "                                                                        \n",
    "b = torch.chunk(a, 5, 0)\n",
    "                                                                        \n",
    "print(b)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.26 – Chunking/chopping tensors continued\n",
    "d = torch.chunk(a, 3, 0)\n",
    "print(d)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.27 – Splitting tensors\n",
    "a = torch.zeros(10,1)\n",
    "\n",
    "print(a)\n",
    "print(a.shape)\n",
    "\n",
    "                                                                        \n",
    "b = torch.split(a,2,0)\n",
    "                                                                        \n",
    "print(b)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.28 – Extracting parts of tensors using index_select\n",
    "a = torch.FloatTensor([[1 ,2, 3],[4, 5, 6], [7, 8, 9]])\n",
    "\n",
    "print(a)\n",
    "print(a.shape)\n",
    "                \n",
    "index = torch.LongTensor([0, 1])\n",
    "                \n",
    "b = torch.index_select(a, 0, index)\n",
    "                \n",
    "print(b)\n",
    "print(b.shape)\n",
    "\n",
    "                        \n",
    "c = torch.index_select(a, 1, index)\n",
    "                        \n",
    "print(c)\n",
    "                                \n",
    "print(c.shape)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.29 – Selecting elements from a tensor using masked_select\n",
    "a = torch.FloatTensor([[1 ,2, 3],[4, 5, 6], [7, 8, 9]])\n",
    "\n",
    "print(a)\n",
    "print(a.shape)\n",
    "                \n",
    "mask = torch.ByteTensor([[0, 1, 0],[1, 1, 1],[0, 1, 0]])\n",
    "                \n",
    "print(mask)\n",
    "print(mask.shape)\n",
    "\n",
    "                                \n",
    "b = torch.masked_select(a, mask)\n",
    "                                \n",
    "print(b)\n",
    "                                \n",
    "print(b.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.30 – Reshaping tensor with squeeze\n",
    "a = torch.zeros(2,2,1)\n",
    "\n",
    "print(a)\n",
    "\n",
    "print(a.shape)\n",
    "                          \n",
    "b = a.squeeze()\n",
    "                          \n",
    "print(b)\n",
    "                                  \n",
    "print(b.shape)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.31 – Reshaping tensor with unsqueeze\n",
    "\n",
    "a = torch.zeros(2,2)\n",
    "\n",
    "print(a)\n",
    "print(a.shape)\n",
    "        \n",
    "b = torch.unsqueeze(a, 0)\n",
    "        \n",
    "print(b)\n",
    "                 \n",
    "print(b.shape)\n",
    "                 \n",
    "c = torch.unsqueeze(a, 1)\n",
    "                 \n",
    "print(c)\n",
    "                         \n",
    "print(c.shape)\n",
    "                         \n",
    "d = torch.unsqueeze(a, 2)\n",
    "                         \n",
    "print(d)\n",
    "                                                   \n",
    "print(d.shape)\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.32 – Extracting parts of a tensor using unbind\n",
    "a\n",
    "print(a.shape)\n",
    "\n",
    "                \n",
    "print(torch.unbind(a, 0))\n",
    "\n",
    "                \n",
    "print(torch.unbind(a, 1))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.33 – Constructing tensor from existing tensor using where clause\n",
    "a = torch.zeros(3,3)\n",
    "\n",
    "print(a)\n",
    "print(a.shape)\n",
    "                \n",
    "b = torch.ones(3,3)\n",
    "                \n",
    "print(b)\n",
    "\n",
    "print(b.shape)\n",
    "                                \n",
    "c = torch.rand(3,3)\n",
    "                                \n",
    "print(c)\n",
    "                                                \n",
    "print(c.shape)\n",
    "\n",
    "                                                \n",
    "d = torch.where(c > 0.5, a, b)\n",
    "                                                \n",
    "print(d)\n",
    "print(d.shape)\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.34 – Logical operations on tensors using any and all\n",
    "a = torch.rand(3,3)\n",
    "\n",
    "print(a)\n",
    "print(a.shape)\n",
    "\n",
    "print(torch.any(a > 0))\n",
    "\n",
    "                \n",
    "print(torch.any(a > 1.0))\n",
    "                \n",
    "print(torch.all(a > 0))\n",
    "                \n",
    "print(torch.all(a > 1.0))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.35 – Reshaping tensors\n",
    "a = torch.arange(1,10)\n",
    "\n",
    "print(a)\n",
    "\n",
    "b = a.view(3,3)\n",
    "\n",
    "print(b)\n",
    "print(b.shape)\n",
    "                \n",
    "c = a.view(3,-1)\n",
    "                \n",
    "print(c)\n",
    "print(c.shape)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.36 – Collapsing the dimensions of a tensor using flatten\n",
    "a = torch.ones([2,2,2,2])\n",
    "print(a)\n",
    "print(a.shape)\n",
    "\n",
    "\n",
    "b = torch.flatten(a)\n",
    "\n",
    "print(b)\n",
    "\n",
    "print(b.shape)\n",
    "\n",
    "c = torch.flatten(a, start_dim=0)\n",
    "\n",
    "print(c)\n",
    "print(c.shape)\n",
    "\n",
    "\n",
    "d = torch.flatten(a, start_dim=1)\n",
    "\n",
    "print(d)\n",
    "print(d.shape)\n",
    "\n",
    "e = torch.flatten(a, start_dim=2)\n",
    "print(e)\n",
    "print(e.shape)\n",
    "\n",
    "f = torch.flatten(a, start_dim=3)\n",
    "    \n",
    "print(f)\n",
    "print(f.shape)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.37 – Extract values from tensor using gather\n",
    "a = torch.rand(4,4)\n",
    "\n",
    "print(a)\n",
    "print(a.shape)\n",
    "\n",
    "                        \n",
    "b = torch.LongTensor([[0,1,2,3]])\n",
    "                        \n",
    "print(b)\n",
    "print(b.shape)\n",
    "\n",
    "c = a.gather(0,b)\n",
    "print(c)\n",
    "                        \n",
    "print(c.shape)\n",
    "                        \n",
    "d = torch.LongTensor([[0],[1],[2],[3]])\n",
    "                        \n",
    "print(d)\n",
    "print(d.shape)\n",
    "\n",
    "                                                \n",
    "e = a.gather(1,d)\n",
    "                                                \n",
    "print(e)\n",
    "print(e.shape)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.38 – Augmenting tensors values with scatter\n",
    "a = torch.rand(4,4)\n",
    "\n",
    "print(a)\n",
    "\n",
    "print(a.shape)\n",
    "\n",
    "                        \n",
    "index = torch.LongTensor([[0,1,2,3]])\n",
    "                        \n",
    "print(index)\n",
    "\n",
    "                        \n",
    "print(index.shape)\n",
    "                        \n",
    "values = torch.zeros(1,4)\n",
    "                        \n",
    "print(values)\n",
    "                        \n",
    "print(values.shape)\n",
    "                        \n",
    "result = a.scatter(0, index, values)\n",
    "                        \n",
    "print(result)\n",
    "\n",
    "print(result.shape)\n",
    "\n",
    "print(a)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Mathematical Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.39 – Validating if given tensors are within a tolerance level\n",
    "a = torch.rand(3,3)\n",
    "\n",
    "print(a)\n",
    "\n",
    "b = a + a * 1e-3\n",
    "                \n",
    "print(b)\n",
    "\n",
    "print(torch.allclose(a,b,rtol=1e-1))\n",
    "\n",
    "                                \n",
    "print(torch.allclose(a,b,rtol=1e-2))\n",
    "                                \n",
    "print(torch.allclose(a,b,rtol=1e-3))\n",
    "\n",
    "                                \n",
    "print(torch.allclose(a,b,rtol=1e-4))\n",
    "                                \n",
    "print(torch.allclose(a,b,atol=1e-1))\n",
    "                                \n",
    "print(torch.allclose(a,b,atol=1e-2))\n",
    "                                \n",
    "print(torch.allclose(a,b,atol=1e-3))\n",
    "                                \n",
    "print(torch.allclose(a,b,atol=1e-4))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.40 – Extracting dimensions of minimum and maximum values in a given tensor\n",
    "a = torch.rand(3,3)\n",
    "\n",
    "print(a)\n",
    "print(a.shape)\n",
    "                \n",
    "print(torch.argmax(a, dim=0))\n",
    "\n",
    "                \n",
    "print(torch.argmax(a, dim=1))\n",
    "                \n",
    "print(torch.argmin(a, dim=0))\n",
    "                \n",
    "print(torch.argmin(a, dim=1))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.41 – Extracting the indices of sorted values of a tensor\n",
    "a = torch.rand(3,3)\n",
    "\n",
    "print(a)\n",
    "print(a.shape)\n",
    "\n",
    "                \n",
    "print(torch.argsort(a, dim=0))\n",
    "\n",
    "print(torch.argsort(a, dim=1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.42 – Cumulative sum along a dimension of the tensor\n",
    "a = torch.rand(3,3)\n",
    "\n",
    "print(a)\n",
    "print(a.shape)\n",
    "                \n",
    "b = torch.cumsum(a, dim=0)\n",
    "                \n",
    "print(b)\n",
    "print(b.shape)\n",
    "\n",
    "c = torch.cumsum(a, dim=1)\n",
    "                                \n",
    "print(c)\n",
    "print(c.shape)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.43 – Cumulative product along a dimension of the tensor\n",
    "a = torch.rand(3,3)\n",
    "\n",
    "print(a)\n",
    "print(a.shape)\n",
    "                \n",
    "b = torch.cumprod(a, dim=0)\n",
    "                \n",
    "print(b)\n",
    "                                \n",
    "print(b.shape)\n",
    "                                \n",
    "c = torch.cumprod(a, dim=1)\n",
    "                                \n",
    "print(c)\n",
    "print(c.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.44 – Absolute value of a tensor\n",
    "a = torch.tensor([[1,-1,1],[1,-1,1],[1,-1,1]])\n",
    "\n",
    "print(a)\n",
    "\n",
    "b = torch.abs(a)\n",
    "print(b)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.45 – Clamping values within a tensor\n",
    "a = torch.rand(3,3)\n",
    "\n",
    "print(a)\n",
    "\n",
    "b = torch.clamp(a, min=0.25, max=0.50)\n",
    "                \n",
    "print(b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.46 – Ceil and floor operations within a tensor\n",
    "a = torch.rand(3,3) * 100\n",
    "\n",
    "print(a)\n",
    "\n",
    "b = torch.floor(a)\n",
    "                \n",
    "print(b)\n",
    "\n",
    "c = torch.ceil(a)\n",
    "                                \n",
    "print(c)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Element-wise Mathematical Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.47 – Element wise multiplication\n",
    "a = torch.rand(3,3)\n",
    "print(a)\n",
    "                \n",
    "b = torch.FloatTensor([[0, 1, 0],[1,1,1],[0,1,0]])\n",
    "print(b)                                \n",
    "\n",
    "c = torch.mul(a,b)                                \n",
    "print(c)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.48 – Element wise division\n",
    "a = torch.rand(3,3)\n",
    "\n",
    "print(a)                \n",
    "b = torch.FloatTensor([[1, 2, 1],[2,2,2],[1,2,1]])\n",
    "                \n",
    "print(b)                                \n",
    "\n",
    "c = torch.div(a,b)\n",
    "print(c)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Trigonometric Operations in Tensors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.49 – Basic Trigonometric operations for tensors\n",
    "a = torch.linspace(-1.0, 1.0, steps=10)\n",
    "\n",
    "print(a)\n",
    "\n",
    "print(torch.sin(a))\n",
    "                  \n",
    "print(torch.cos(a))\n",
    "\n",
    "print(torch.tan(a))\n",
    "                                   \n",
    "print(torch.asin(a))\n",
    "                                            \n",
    "print(torch.acos(a))\n",
    "                                                    \n",
    "print(torch.atan(a))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.50 – Additional Trigonometric operations for tensors\n",
    "a = torch.linspace(-1.0, 1.0, steps=10)\n",
    "\n",
    "print(a)\n",
    "         \n",
    "print(torch.sigmoid(a))\n",
    "                 \n",
    "print(torch.tanh(a))\n",
    "                          \n",
    "print(torch.log1p(a))\n",
    "                                   \n",
    "print(torch.erf(a))\n",
    "                                            \n",
    "print(torch.erfinv(a))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comparison operations for tensors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.51 – Comparison operations for tensors\n",
    "a = torch.rand(3,3)\n",
    "\n",
    "print(a)               \n",
    "\n",
    "b = torch.rand(3,3)\n",
    "                \n",
    "print(b)                                \n",
    "\n",
    "print(torch.ge(a,b))\n",
    "\n",
    "print(torch.le(a,b))\n",
    "                                                                \n",
    "print(torch.eq(a,b))\n",
    "\n",
    "print(torch.ne(a,b))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Linear Algebraic Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.52 – Matrix multiplication operations for tensors\n",
    "a = torch.ones(2,3)\n",
    "print(\"a -\")\n",
    "print(a)\n",
    "print(a.shape)\n",
    "\n",
    "print(\"b -\")\n",
    "b = torch.ones(3,2)\n",
    "print(b)\n",
    "print(b.shape)\n",
    "\n",
    "print(\"\\nMultiplication - \")\n",
    "print(torch.matmul(a,b))\n",
    "\n",
    "#The `addbmm` function (where bmm stands for batch matrix-matrix product) allows us to perform the computation `p * m + q * [a1 * b1 + a2 * b2 + ...]` where `p` and `q` are scalars and `m`, `a1`, `b1` , `a2` and `b2` are tensors. Note that the `addbmm` takes parameters `p` and `q` with default values equal to one and that tensors such as `a1` and `a2` are provided by stacking them along the first dimension. The example below illustrates the same.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.53 – Batch matrix matrix addition of tensors\n",
    "a = torch .ones(2, 2, 3)\n",
    "print(\"a-\")\n",
    "print(a)                          \n",
    "print(a.shape)\n",
    "\n",
    "b = torch.ones(2, 3, 2)\n",
    "print(\"b-\")\n",
    "print(b)\n",
    "print(b.shape)\n",
    "\n",
    "print(\"m-\")\n",
    "m = torch.ones(2,2)\n",
    "                                                                      \n",
    "print(m)                                                                              \n",
    "print(m.shape)\n",
    "                                                                              \n",
    "\n",
    "print(\"BMM- 2 .  3\")\n",
    "print(torch.addbmm(2, m, 3, a, b))\n",
    "\n",
    "print(\"BMM- 1 .  1\")\n",
    "print(torch.addbmm(1, m, 1, a, b))\n",
    "\n",
    "\n",
    "print(\"BMM-\")\n",
    "print(torch.addbmm(m, a, b))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.54 –Non Batch matrix matrix addition of tensors\n",
    "a = torch.ones(2, 3)\n",
    "print(\"a-\") \n",
    "print(a)\n",
    "print(a.shape)\n",
    "\n",
    "b = torch.ones(3, 2)\n",
    "print(\"b-\")        \n",
    "print(b)\n",
    "print(b.shape)\n",
    "\n",
    "\n",
    "m = torch.ones(2,2)\n",
    "print(\"m-\") \n",
    "                        \n",
    "print(m)\n",
    "print(m.shape)\n",
    "\n",
    "print(\"ADDMM\")\n",
    "print(torch.addmm(m, a, b))\n",
    "\n",
    "print(\"ADDMM - 2 . 3\")\n",
    "print(torch.addmm(2, m, 3, a, b))\n",
    "\n",
    "print(\"ADDMM - 1 . 1\")\n",
    "print(torch.addmm(1, m, 1, a, b))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.55 – matrix vector addition of tensors\n",
    "\n",
    "a = torch.ones(2, 3)\n",
    "\n",
    "print(\"a - \")        \n",
    "print(a)\n",
    "print(a.shape)\n",
    "\n",
    "\n",
    "b = torch.ones(3)\n",
    "print(\"b - \")        \n",
    "print(b)\n",
    "print(b.shape)\n",
    "\n",
    "m = torch.ones(2)\n",
    "print(\"m - \")        \n",
    "print(m)\n",
    "print(m.shape)\n",
    "        \n",
    "print(\"ADDMV- 2 . 2\")        \n",
    "torch.addmv(2,m,3,a,b)\n",
    "\n",
    "print(\"ADDMV- 1 . 1\")                \n",
    "torch.addmv(1,m,1,a,b)\n",
    "\n",
    "print(\"ADDMV-\")   \n",
    "torch.addmv(m,a,b)\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.56 – Outer product of vectors\n",
    "a = torch.tensor([1.0, 2.0, 3.0])\n",
    "print(\"a - \")        \n",
    "print(a)\n",
    "print(a.shape)\n",
    "\n",
    "\n",
    "b = a\n",
    "\n",
    "m = torch.ones(3,3)\n",
    "print(\"m - \")        \n",
    "print(m)\n",
    "print(m.shape)\n",
    "\n",
    "print(\"ADDR -\")                    \n",
    "print(torch.addr(m,a,b))\n",
    "        \n",
    "m = torch.zeros(3,3)\n",
    "\n",
    "print(\"m - \")        \n",
    "print(m)\n",
    "                \n",
    "print(\"ADDR -\")    \n",
    "print(torch.addr(m,a,b))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.57 – Illustration for baddbmm\n",
    "a = torch.ones(2,2,3)\n",
    "print(\"a - \")        \n",
    "print(a)\n",
    "print(a.shape)\n",
    "                          \n",
    "b = torch.ones(2,3,2)\n",
    "print(\"b - \")        \n",
    "print(b)\n",
    "print(b.shape)                                                                      \n",
    "\n",
    "\n",
    "m = torch.ones(2, 2, 2)\n",
    "print(\"m - \")        \n",
    "print(m)\n",
    "print(m.shape)                                                                                                \n",
    "\n",
    "print(\"BADDBMM - 1,m,1,a,b\")\n",
    "print(torch.baddbmm(1,m,1,a,b))\n",
    "\n",
    "print(\"BADDBMM - 2,m,1,a,b\")\n",
    "print(torch.baddbmm(2,m,1,a,b))\n",
    "\n",
    "print(\"BADDBMM - 1,m,3,a,b\")\n",
    "print(torch.baddbmm(1,m,2,a,b))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.58 – Illustration for batch-wise matrix multiplication\n",
    "a = torch.ones(2,2,3)\n",
    "print(\"a - \")        \n",
    "print(a)\n",
    "print(a.shape)                                                                                                \n",
    "\n",
    "b = torch.ones(2,3,2)\n",
    "print(\"b - \")        \n",
    "print(b)\n",
    "print(b.shape)                                                                                                \n",
    "\n",
    "print(\"BMM a.b\")\n",
    "print(torch.bmm(a,b))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.59 – Dot product for tensors\n",
    "a = torch.rand(3)\n",
    "print(\"a-\")\n",
    "print(a)\n",
    "\n",
    "b = torch.rand(3)\n",
    "print(\"b-\")\n",
    "print(b)\n",
    "\n",
    "print(\"Dot -- A.b\")\n",
    "print(torch.dot(a,b))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.60 – Compute Eigen values\n",
    "a = torch.rand(3,3)\n",
    "\n",
    "print(\"a-\",a)                \n",
    "\n",
    "values, vectors = torch.eig(a, eigenvectors=True)\n",
    "                \n",
    "print(\"Values:\",values)\n",
    "                                \n",
    "print(\"Vectors:\",vectors)\n",
    "   \n",
    "\n",
    "print(values[0,0] * vectors[:,0].reshape(3,1))\n",
    "\n",
    "print(torch.mm(a, vectors[:,0].reshape(3,1)))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.61 – Cross product of two tensors\n",
    "a = torch.rand(3)\n",
    "b = torch.rand(3)\n",
    "\n",
    "print(\"Cross Product \")\n",
    "print(torch.cross(a,b))\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.62 – Compute the norm of a tensor\n",
    "\n",
    "a = torch.ones(4)\n",
    "\n",
    "print(\"Norm(a,1)-\")\n",
    "print(torch.norm(a,1))\n",
    "\n",
    "print(\"Norm(a,2)-\")\n",
    "print(torch.norm(a,2))\n",
    "\n",
    "print(\"Norm(a,3)-\")\n",
    "print(torch.norm(a,3))\n",
    "\n",
    "print(\"Norm(a,4)-\")\n",
    "print(torch.norm(a,4))\n",
    "\n",
    "print(\"Norm(a,5)-\")\n",
    "print(torch.norm(a,5))\n",
    "\n",
    "print(\"Norm(a,Inf)-\")\n",
    "print(torch.norm(a,float('inf')))\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Listing 2.63 – Normalize a tensor\n",
    "a = torch.FloatTensor([[1,2,3,4]])\n",
    "print(\"a-\")\n",
    "print(a)\n",
    "\n",
    "print(\"renorm(a)\")\n",
    "print(torch.renorm(a, dim=0, p=2, maxnorm=1))\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
